package com.sprometheus.core.handler;

import cn.hutool.core.date.StopWatch;
import cn.hutool.core.util.ObjUtil;
import com.sprometheus.core.constants.AbstractTitle;
import com.sprometheus.core.constants.SPrometheusTitleEnum;
import com.sprometheus.core.anno.Msg;
import com.sprometheus.core.anno.SPrometheus;
import com.sprometheus.core.constants.Scene;
import com.sprometheus.core.pojo.Exp;
import com.sprometheus.core.pojo.GlobalExceptionBO;
import com.sprometheus.core.pojo.Groups;
import com.sprometheus.core.pojo.SPrometheusException;
import com.sprometheus.event.GlobalExpEventPublisher;
import com.sprometheus.strategy.RoutingStrategy;
import com.sprometheus.util.GenerateUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * 异常链路处理工具
 *
 * @author 风白羽
 * @date 2024/9/5 下午4:56
 */
@Slf4j
@Component
public class SPrometheusHandler {

    @Value("${spring.application.name}")
    private String serverName;
    @Value("${spring.profiles.active}")
    private String active;
    @Value("${sprometheus.default}")
    private String defaultChannel;
    @Resource
    private Map<String, RoutingStrategy> routingStrategyMap;
    @Autowired
    private GlobalExpEventPublisher globalExpEventPublisher;

    /***
     * 业务异常
     *
     * 可预知的异常，如：参数校验异常，业务流程中提前被定义的异常等
     * 这种异常的等级并不是特别高，有些需要业务人员参与关注，而大多数情况下，业务人员不需要关注
     * 所以这种异常需要控制通知的频率以及通知的范围，避免群消息过于繁多，导致在关键时候找不到想要关注的重要消息
     *
     * Business exception.
     *
     * Predictable exceptions, such as parameter check exceptions, exceptions defined in advance in business processes, etc.
     * The level of this exception is not particularly high, some require business staff to participate in attention, and in most cases, business staff do not need to pay attention to.
     *
     * Therefore, this exception needs to control the frequency and scope of notifications to avoid too many group messages, resulting in the failure to find important messages that you want to pay attention to at critical times.
     *
     * @see GlobalExceptionBO
     *
     * @param point
     * @param watch
     * @param e
     */
    @Msg(msg = "该方法提供给切面使用，手动方式无需调用")
    public void handlePrometheusExp(JoinPoint point, StopWatch watch, SPrometheusException e) {
        RoutingStrategy routingStrategy = routingStrategyMap.get(defaultChannel);
        if (routingStrategy == null) return;
        MethodSignature sign = (MethodSignature) point.getSignature();
        GlobalExceptionBO exp = new GlobalExceptionBO(e, routingStrategy);
        //系统相关参数
        expSysInfo(point, watch, exp);
        //请求相关参数
        expRequestInfo(sign, exp);
        //推送至业务飞书群
        businessSend(e.getCode(), exp, routingStrategy);
    }

    /***
     * 该方法用于监听器注解
     * 由于SPrometheusException Exception都有固定的调用方式
     *
     * 作用：避免和由于SPrometheusException冲突 重复发送消息
     *
     * @param point
     * @param watch
     * @param e
     */
    @Msg(msg = "该方法用于监听器注解@Prometheus")
    public void handlePrometheusOtherExp(JoinPoint point, StopWatch watch, Exception e) {
        RoutingStrategy routingStrategy = routingStrategyMap.get(defaultChannel);
        if (routingStrategy == null) return;
        MethodSignature sign = (MethodSignature) point.getSignature();
        GlobalExceptionBO exp = new GlobalExceptionBO(e, routingStrategy);
        //系统相关参数
        expSysInfo(point, watch, exp);
        //获取请求IP
        expIp(exp);
        //接口描述
        SPrometheus sPrometheus = sign.getMethod().getAnnotation(SPrometheus.class);
        AbstractTitle titleEnum = sPrometheus.title();
        exp.setUri(sPrometheus.uri());
        exp.setCode(titleEnum.code());
        exp.setTitle(titleEnum.title());
        exp.setMonitorType(titleEnum.type());
        //推送至业务群
        if (sPrometheus.scene() == Scene.BUSINESS) {
            businessSend(exp.getCode(), exp, routingStrategy);
        } else {
            devSend(exp, routingStrategy);
        }
    }

    /***
     * 系统异常
     *
     * 无法预估的异常，如：空指针异常，类型转换错误，sql执行异常，超时等
     * 如果出现这种情况，那么这种异常需要及时处理，异常的等级也是最高的P0级别
     *
     * Unpredictable exceptions, such as null pointer exception, type conversion error, sql execution exception, timeout, etc.
     * If this happens, the exception needs to be handled in time, and the exception level is the highest P0 level.
     *
     * @see GlobalExceptionBO
     *
     * @param point
     * @param watch
     * @param e
     */
    @Msg(msg = "该方法提供给切面使用，手动方式无需调用")
    public void handleDefault(JoinPoint point, StopWatch watch, Throwable e) {
        RoutingStrategy routingStrategy = routingStrategyMap.get(defaultChannel);
        if (routingStrategy == null) return;
        MethodSignature sign = (MethodSignature) point.getSignature();
        GlobalExceptionBO exp = new GlobalExceptionBO(e, routingStrategy);
        //系统相关参数
        expSysInfo(point, watch, exp);
        //请求相关参数
        expRequestInfo(sign, exp);
        expIp(exp);
        exp.setCode(SPrometheusTitleEnum.OTHER.getCode());
        exp.setTitle(SPrometheusTitleEnum.OTHER.getTitle());
        exp.setMonitorType(SPrometheusTitleEnum.OTHER.getType());
        //推送至开发群
        devSend(exp, routingStrategy);
    }

    /***
     * 手动添加异常监控，发送至飞书
     * 手动传入功能描述（title）
     * 但无请求地址
     */
    @Msg(msg = "手动调用时调用该方法，手动传入title和uri")
    public void insert(Exp req) {
        RoutingStrategy routingStrategy = routingStrategyMap.get(req.getSendChannel());
        if (routingStrategy == null) return;
        GlobalExceptionBO exp = new GlobalExceptionBO(req, routingStrategy);
        //系统相关参数
        exp.setActive(active);
        exp.setServerName(serverName);
        //如果调用者类为空或者调用者方法为空 则不赋值即可
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (ObjUtil.isNotNull(requestAttributes)) {
            exp.setIp(GenerateUtil.getForwardedIp(requestAttributes.getRequest()));
        }
        if (Objects.equals(req.getTitleEnum().getCode(), SPrometheusTitleEnum.OTHER.getCode())) {
            //推送至【开发】群
            devSend(exp, routingStrategy);
        } else {
            //推送至【业务】群
            businessSend(req.getTitleEnum().getCode(), exp, routingStrategy);
        }
    }

    @Msg(msg = "该方法会保留原格式msg")
    public void insert(Exp req, Scene scene) {
        RoutingStrategy routingStrategy = routingStrategyMap.get(req.getSendChannel());
        if (routingStrategy == null) return;
        GlobalExceptionBO exp = new GlobalExceptionBO(req, routingStrategy);
        //系统相关参数
        exp.setActive(active);
        exp.setServerName(serverName);
        //如果调用者类为空或者调用者方法为空 则不赋值即可
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (ObjUtil.isNotNull(requestAttributes)) {
            exp.setIp(GenerateUtil.getForwardedIp(requestAttributes.getRequest()));
        }
        //推送至业务飞书群
        devBusiness(req.getTitleEnum().getCode(), exp, scene, routingStrategy);
    }

    /***
     * 系统相关参数
     * @param point
     * @param watch
     * @param exp
     */
    private void expSysInfo(JoinPoint point, StopWatch watch, GlobalExceptionBO exp) {
        exp.setActive(active);
        exp.setServerName(serverName);
        exp.setReqData(GenerateUtil.getParameter(point));
        exp.setConsumingTime(watch.getTotalTimeSeconds() + " 秒");
    }

    /**
     * IP信息
     *
     * @param exp
     */
    private void expIp(GlobalExceptionBO exp) {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (ObjUtil.isNotNull(requestAttributes)) {
            HttpServletRequest request = requestAttributes.getRequest();
            exp.setIp(GenerateUtil.getForwardedIp(request));
        }
    }

    /***
     * 请求相关参数
     * @param sign
     * @param exp
     */
    private void expRequestInfo(MethodSignature sign, GlobalExceptionBO exp) {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (ObjUtil.isNotNull(requestAttributes)) {
            HttpServletRequest request = requestAttributes.getRequest();
            exp.setIp(GenerateUtil.getForwardedIp(request));
            exp.setUri(GenerateUtil.getApiRequestUri(sign.getDeclaringType(), sign.getMethod(), request.getRequestURI()));
        }
    }

    /***
     * 推送至开发群
     * @param exp
     */
    private void devSend(GlobalExceptionBO exp, RoutingStrategy routingStrategy) {
        if (routingStrategy == null) return;
        Set<Groups> groups = routingStrategy.getDeveloper();
        for (Groups group : groups) {
            send(exp, Scene.DEV, routingStrategy);
        }
    }

    /***
     * 推送至业务群
     * @param code
     * @param exp
     */
    private void businessSend(Integer code, GlobalExceptionBO exp, RoutingStrategy routingStrategy) {
        for (Groups group : routingStrategy.getBusiness()) {
            //是否是需要发送至对应群的错误码
            if (group.getCode().contains("," + code + ",")) {
                send(exp, Scene.BUSINESS, routingStrategy);
            }
        }
    }

    /***
     * 推送至指定类型的群
     * @param code
     * @param exp
     * @param scene
     * @param routingStrategy
     */
    private void devBusiness(Integer code, GlobalExceptionBO exp, Scene scene, RoutingStrategy routingStrategy) {
        for (Groups group : routingStrategy.getBusiness()) {
            //是否是需要发送至对应群的错误码
            if (group.getCode().contains("," + code + ",")) {
                send(exp, scene, routingStrategy);
            }
        }
    }

    /***
     * event实现发布订阅
     * @param message
     * @param scene
     * @param routingStrategy
     */
    private void send(GlobalExceptionBO message, Scene scene, RoutingStrategy routingStrategy) {
        if (routingStrategy.isEnabled()) return;
        message.setScene(scene.toString());
        globalExpEventPublisher.publishCustomEvent(message);
    }
}
